package ws.utils;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Some array utilities.
 *
 * @author Wilson Santos, 36228 <wilson.santos@ua.pt>
 */
public class ArrayUtil {

  private static final Random rand = new Random();
  /** 16-bit UCS Transformation Format, little-endian byte order. */
  private static final String charsetName = "UTF-16LE";

  /**
   * Generate an byte array with random values.
   *
   * @param size size of the array.
   *
   * @return byte array with random values.
   */
  public static byte[] genRandomByteArray(int size) {
    byte[] ret = new byte[size];
    rand.nextBytes(ret);
    return ret;
  }

  /**
   * Convert an byte array to a string representing the bytes in hexadecimal format.
   *
   * @param array array to convert.
   */
  public static String toHexadecimal(byte[] array) {
    if (array != null && array.length > 0) {
      String ret = "";
      ret += "[0x" + Integer.toHexString(0x0FF & array[0]);
      for (int i = 1; i < array.length; ++i) {
        ret += ", 0x" + Integer.toHexString(0x0FF & array[i]);
      }
      ret += "]";
      return ret;
    } else {
      return "";
    }
  }

  /**
   * Print an byte array on hexadecimal format.
   *
   * @param array array to print.
   */
  public static void printHex(byte[] array) {
    if (array != null && array.length > 0) {
      System.out.printf("{%2x", array[0]);
      for (int i = 1; i < array.length; ++i) {
        System.out.printf(", %2x", array[i]);
      }
      System.out.println("}");
    } else {
      return;
    }
  }

  /**
   * Print an byte array on hexadecimal format.
   * Actually, it also prints the decimal format.
   * The values are distributed in 10 columns.
   *
   * @param array array to print.
   */
  public static void printHexAndDecimal(byte[] array) {
    printHexAndDecimal(array, array.length);
  }

  /**
   * Print an byte array on hexadecimal format.
   * Actually, it also prints the decimal format.
   * The values are distributed in 10 columns.
   *
   * @param array array to print.
   * @param size number of bytes to print.
   */
  public static void printHexAndDecimal(byte[] array, int size) {
    int idx1 = 0, idx2 = 0, columnsPerLine = 10;
    while (idx1 < size) {
      for (int i = 0; i < columnsPerLine; ++i) {
        if (idx1 < array.length) {
          System.out.printf("%2x ", array[idx1++]);
        } else {
          System.out.printf("%2c ", 0x20);
        }
      }
      System.out.print("\t.......\t");
      for (int i = 0; i < columnsPerLine; ++i) {
        if (idx2 < array.length) {
          if (array[idx2] >= 0x20 && array[idx2] <= 0x7E) {
            System.out.printf("%4c ", array[idx2++]);
          } else {
            System.out.printf("%4d ", array[idx2++]);
          }
        } else {
          System.out.printf("%4c ", 0x20);
        }
      }
      System.out.println("");
    }
  }

  /**
   * Constructs a new {@code String} by decoding the specified array of bytes
   * using the charset {@code UTF-16LE}.
   *
   * @param bytes the bytes to be decoded into characters.
   *
   * @return {@code String} with the decoding result.
   */
  public static String fromLittleEndian(byte[] bytes) {
    try {
      return new String(bytes, charsetName);
    } catch (UnsupportedEncodingException ex) {
      throw new RuntimeException(ex);
    }
  }

  /**
   * Get the byte array representation of one string in little-endian unicode.
   * The {@link #charsetName} dictates the output format.
   *
   * @param val String to represent.
   *
   * @return Byte array representation.
   */
  public static byte[] toLittleEndian(String val) {
    if (val == null || val.length() == 0) {
      return new byte[0];
    }
    try {
      return ByteBuffer.allocate(val.length() * 2).put(val.getBytes(charsetName)).array();
    } catch (UnsupportedEncodingException ex) {
      throw new RuntimeException(ex);
    }
  }

  /**
   * Get the byte array representation of one integer.
   *
   * @param val Integer to represent.
   *
   * @return Byte array representation of one integer.
   */
  public static byte[] toLittleEndian(int val) {
    return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(val).array();
  }

  /**
   * Get the byte array representation of one short.
   *
   * @param val Short to represent.
   *
   * @return Byte array representation of one short.
   */
  public static byte[] toLittleEndian(short val) {
    return ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(val).array();
  }
}
